﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using JasLib.Extensions;
using JasLib.Interop.Win32;

namespace JasLib.Interop
{
    /// <summary>
    /// Eliminates the hassle of bookkeeping when performing multiple native memory allocations
    /// in a tightly-scoped section of code.
    /// All allocations made through the group are freed automatically in the finalizer.
    /// This class is thread-safe.
    /// </summary>
    /// <remarks>
    /// TODO: Make this a growing linear span of pages (similar to the CLR heap)
    /// rather than fragmenting the Win32 heap,
    /// especially because nothing is freed until everything is freed on disposal.
    /// </remarks>
    [DebuggerDisplay("{_pointers.Count} allocations, {_lTotalBytesAllocated} bytes total")]
    public class NativeHeapAllocationGroup : FinalizedDisposableObject, IMalloc
    {
        private readonly List<UINT_PTR> _pointers = new List<UINT_PTR>();
        private readonly List<object> _references = new List<object>();

        private UINT_PTR _uiTotalBytesAllocated = UINT_PTR.Zero;
        public UINT_PTR TotalBytesAllocated
        {
            get { return _uiTotalBytesAllocated; }
        }

        /***************************************************************************/
        public NativeHeapAllocationGroup(int iInitialCapacity = 0)
        {
        }

        /***************************************************************************/
        protected override void Dispose(bool bFinalizer)
        {
            lock (_pointers)
            {
                for (int iIndex = 0; iIndex < _pointers.Count; iIndex++)
                {
                    if (_pointers[iIndex] != UINT_PTR.Zero)
                    {
                        KERNEL32.HeapFree(_pointers[iIndex]);
                        _pointers[iIndex] = UINT_PTR.Zero;
                    }
                }
                _pointers.Clear();
            }
            return;
        }

        #region ******* IMalloc implementation ************************************************

        public UINT_PTR Alloc(UINT_PTR cb)
        {
            lock (_pointers)
            {
                _pointers.Add(UINT_PTR.Zero);
                UINT_PTR pBuffer = _pointers[_pointers.Count - 1] = KERNEL32.HeapAlloc(cb);
                if (pBuffer == UINT_PTR.Zero)
                    throw new Win32Exception();
                _uiTotalBytesAllocated += cb;
                return pBuffer;
            }
        }

        public UINT_PTR Realloc(UINT_PTR pv, UINT_PTR cb)
        {
            UINT_PTR pNewBuffer = Alloc(cb);
            MSVCRT.memcpy(pNewBuffer, pv, cb);
            return pNewBuffer;
        }

        public void Free(UINT_PTR pv)
        {
            /// We only free everything on destruction.
            return;
        }

        public UINT_PTR GetSize(UINT_PTR pv)
        {
            throw new NotImplementedException();
        }

        public int DidAlloc(UINT_PTR pv)
        {
            return -1;
        }

        public void HeapMinimize()
        {
            /// The heap is automatically compacted on account of being a simple stack.
            return;
        }

        #endregion

        /***************************************************************************/
        /// <summary>
        /// This method treats the provided IEnumerable&lt;T&gt; as an array and then marshals it to a contiguous buffer.
        /// </summary>
        public UINT_PTR AllocArrayCopy<T>(IEnumerable<T> structs, int iStart = 0, int iLength = -1) where T : struct
        {
            IList<T> structList = (structs as IList<T>) ?? structs.ToList();

            if (iLength == -1)
                iLength = structList.Count;

            int iStructSize = MarshalHelper.SizeOf<T>();
            int iTotal = iStructSize * iLength;

            UINT_PTR pBuffer = Alloc(iTotal);

            UINT_PTR ptrOffset = pBuffer;
            for (int iIndex = iStart; iIndex < (iStart + iLength); iIndex++)
            {
                /// Extremely inefficient for scalar types.
                Marshal.StructureToPtr(structList[iIndex], ptrOffset, false);
                ptrOffset += iStructSize;
            }
            return pBuffer;
        }

        /***************************************************************************/
        public UINT_PTR AllocBytesCopy(byte[] buffer, int iStart = 0, int iLength = -1)
        {
            if (buffer == null)
                return UINT_PTR.Zero;

            if (iLength == -1)
                iLength = (buffer.Length - iStart);

            UINT_PTR pBuffer = Alloc(iLength);
            Marshal.Copy(buffer, iStart, pBuffer, iLength);
            return pBuffer;
        }

        /***************************************************************************/
        public UINT_PTR AllocCopy<T>(T thisStruct) where T : struct
        {
            UINT_PTR pBuffer = Alloc(MarshalHelper.SizeOf<T>());
            Marshal.StructureToPtr(thisStruct, pBuffer, false);
            return pBuffer;
        }

        /***************************************************************************/
        public UINT_PTR AllocDoubleNullTerminatedStringCopy(params string[] strings)
        {
            int iCharacterCount;
            return AllocDoubleNullTerminatedStringCopy(strings, out iCharacterCount);
        }

        /***************************************************************************/
        public UINT_PTR AllocDoubleNullTerminatedStringCopy(IEnumerable<string> strings, out int iCharacterCount)
        {
            iCharacterCount = 0;

            char[] chars = strings.JoinDoubleNullTerminated();
            iCharacterCount = chars.Length;
            return AllocArrayCopy(chars);
        }

        /***************************************************************************/
        public UINT_PTR AllocFor<T>() where T : struct
        {
            return Alloc(MarshalHelper.SizeOf<T>());
        }

        /***************************************************************************/
        public UINT_PTR AllocUnicodeCopy(string strSource)
        {
            if (strSource == null)
                return UINT_PTR.Zero;

            unsafe
            {
                char* pBuffer = (char*)Alloc((strSource.Length * 2) + 1);

                int iIndex = 0;
                for (; iIndex < strSource.Length; iIndex++)
                {
                    pBuffer[iIndex] = strSource[iIndex];
                }
                pBuffer[iIndex] = '\0';

                return pBuffer;
            }
        }

        /***************************************************************************/
        public object KeepObjectReference(object obj)
        {
            lock (_references)
            {
                _references.Add(obj);
            }
            return obj;
        }

        /***************************************************************************/
        public UINT_PTR LockDelegate(Delegate del)
        {
            lock (_references)
            {
                _references.Add(del);
            }
            return Marshal.GetFunctionPointerForDelegate(del);
        }
    }
}
